/**
 * @description 轮播图组件
 */
import React, { useState, useRef, useEffect, useReducer, useCallback, useMemo } from 'react';
import './index.scss';

const baseCls = 'admin-carousel';

interface CarouselProps {
    /** 轮播时间，默认三秒 */
    intTime?: number;
    /** 面板指示点位置，默认bottom */
    dotPosition?: 'top' | 'bottom' | 'left' | 'right';
    /** 面板指示点高亮颜色*/
    dotColor?: CSSStyleDeclaration['color'];
    children: Array<JSX.Element>;
}

interface ActionPayload {
    currentIndex: number;
    animation: CSSStyleDeclaration['animation'];
}

interface Action {
    type: 'normal' | 'next' | 'pre';
    payload?: Partial<ActionPayload>;
}

const Carousel: React.FC<CarouselProps> = (props) => {
    const { intTime = 3000, dotPosition = 'bottom', dotColor = '#00CCFF', children } = props;
    const [isNext, setIsNext] = useState(true);
    const [isShow, setIsShow] = useState(false);
    const ref = useRef(null);
    const timer = useRef<any>(null);

    const [state, dispatch] = useReducer(
        (preState: ActionPayload, action: Action) => {
            const { currentIndex: index } = preState;
            const { type, payload } = action;

            switch (type) {
                case 'normal':
                    return { ...preState, ...payload };
                case 'next':
                    return { ...preState, currentIndex: index + 1, ...payload };
                case 'pre':
                    return { ...preState, currentIndex: index - 1, ...payload };
                default:
                    return { ...preState };
            }
        },
        { currentIndex: 1, animation: 'none' }
    );
    const { currentIndex, animation } = state;

    useEffect(() => {
        init();
    }, []);

    useEffect(() => {
        const { currentIndex } = state;
        timer.current && clearInterval(timer.current);
        const time = currentIndex > children.length || currentIndex === 0 ? intTime / 10 : intTime;

        timer.current = setInterval(isNext ? nextSlide : preSlide, time);
        return () => {
            timer.current && clearInterval(timer.current);
        };
    }, [state, isNext]);

    // 初始化操作，复制第一个元素到最后，复制最后一个元素到最前头，解决轮播切换首尾的的不连贯
    const init = useCallback(() => {
        const dom = ref.current as unknown as HTMLDivElement;
        const firstNode = dom?.firstElementChild?.cloneNode(true);
        const lastNode = dom?.lastElementChild?.cloneNode(true);

        dom?.appendChild(firstNode as Node);
        dom?.insertBefore(lastNode as Node, dom?.firstElementChild);

        // 解决flex 布局中，主轴宽度不够，子元素不认宽高的问题
        for (const child of Array.from(dom.children)) {
            (child as any).style.flex = 'none';
        }
    }, []);

    // 下一面板的处理函数
    const nextSlide = () => {
        if (currentIndex > children.length) {
            dispatch({
                type: 'normal',
                payload: {
                    currentIndex: 1,
                    animation: 'none',
                },
            });
            return;
        }

        dispatch({
            type: 'next',
            payload: {
                animation: '0.3s',
            },
        });
    };

    // 上一面板的处理函数
    const preSlide = () => {
        if (currentIndex === 0) {
            dispatch({
                type: 'normal',
                payload: {
                    currentIndex: children.length,
                    animation: 'none',
                },
            });
        } else {
            dispatch({
                type: 'pre',
                payload: {
                    animation: '0.3s',
                },
            });
        }

        setIsNext(currentIndex !== 1);
    };

    // 画板指点是否为水平布局
    const isHorizontal = useMemo(
        () => dotPosition === 'top' || dotPosition === 'bottom',
        [dotPosition]
    );

    const pointsStyle = useMemo(() => {
        const map = {
            top: {
                width: '100%',
                height: 10,
                top: 20,
            },
            bottom: {
                width: '100%',
                height: 10,
                bottom: 20,
            },
            left: {
                width: 10,
                height: '100%',
                bottom: 0,
                left: 20,
            },
            right: {
                width: 10,
                height: '100%',
                bottom: 0,
                right: 20,
            },
        };

        return map[dotPosition] || {};
    }, [dotPosition]);

    return (
        <React.Fragment>
            <div
                className={`${baseCls}`}
                onMouseEnter={() => setIsShow(true)}
                onMouseLeave={() => setIsShow(false)}
            >
                <div
                    className={`${baseCls}-content`}
                    style={{
                        transform: isHorizontal
                            ? `translateX(${-currentIndex * 100}%)`
                            : `translateY(${-currentIndex * 100}%)`,
                        transition: animation,
                        flexDirection: isHorizontal ? 'row' : 'column',
                    }}
                    ref={ref}
                >
                    {React.Children.map(children, (item) => [item])}
                </div>

                <div
                    className={`${baseCls}-points`}
                    style={{ ...pointsStyle }}
                >
                    <div
                        className={`${baseCls}-points-box`}
                        style={{ flexDirection: isHorizontal ? 'row' : 'column' }}
                    >
                        {children.map((_: JSX.Element, index: number) => (
                            <i
                                key={index}
                                className={`${baseCls}-point`}
                                style={{
                                    backgroundColor: currentIndex === index + 1 ? dotColor : '#fff',
                                }}
                                onClick={() =>
                                    dispatch({
                                        type: 'normal',
                                        payload: {
                                            currentIndex: index + 1,
                                        },
                                    })
                                }
                            />
                        ))}
                    </div>
                </div>

                {isHorizontal && (
                    <React.Fragment>
                        <span
                            className={`${baseCls}-preSlide`}
                            style={{ display: isShow ? 'block' : 'none' }}
                            onClick={preSlide}
                        />
                        <span
                            className={`${baseCls}-nextSlide`}
                            style={{ display: isShow ? 'block' : 'none' }}
                            onClick={nextSlide}
                        />
                    </React.Fragment>
                )}
            </div>
        </React.Fragment>
    );
};

export default React.memo(Carousel);
